using System;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

[DefaultProperty("Text"), ToolboxData("<{0}:WebChatCS runat=server></{0}:WebChatCS>")]
class WebChatCS: WebControl,System.Web.UI.ICallbackEventHandler, INamingContainer
{

	//private variables
	//private System.Collections.Generic.Queue _Messages<ChatMessage>;
    Queue<string> _Messages = new Queue<string>();
	private long _LastMsgID = 0;
	private Panel _pnl = new Panel();
	private TextBox _txt = new TextBox();

	//constructor
	public WebChatCS()
	{
		//set default size
		base.Width = new Unit(350, UnitType.Pixel);
		base.Height = new Unit(200, UnitType.Pixel);
	}

#region "public properties"

	/// <summary>
	/// The number of chat messages will be cached in application state
	/// </summary>
	/// <remarks></remarks>
	private int _HistoryCapacity = 10;
	[Bindable(true), Category("Behavior"), DefaultValue(10), Localizable(true), Description("The number of chat messages will be cached in application state")]
	public int HistoryCapacity
	{
		get
		{
			return _HistoryCapacity;
		}
		set
		{
			if (value < 1)
			{
				value = 1;
			}
			_HistoryCapacity = value;
		}
	}

	/// <summary>
	/// Sets or gets whether this control should recieve focus upon page load
	/// </summary>
	/// <remarks></remarks>
	private bool _GetFocus = true;
	[Bindable(true), Category("Behavior"), DefaultValue(true), Localizable(true), Description("Sets or gets whether this control should receive focus upon page load")]
	public bool InitialFocus
	{
		get
		{
			return _GetFocus;
		}
		set
		{
			_GetFocus = value;
		}
	}

	/// <summary>
	/// The number of seconds between chat update requests
	/// </summary>
	/// <remarks></remarks>
	private byte _CallBackInterval = 2;
	[Bindable(true), Category("Behavior"), DefaultValue(typeof(byte), "2"), Localizable(true), Description("The number of seconds between chat update requests")]
	public byte CallBackInterval
	{
		get
		{
			return _CallBackInterval;
		}
		set
		{
			if (value < 1)
			{
				value = 1;
			}
			_CallBackInterval = value;
		}
	}

	/// <summary>
	/// Sets or gets the name of the user that's chatting in this instance of the control
	/// </summary>
	[Bindable(true), Category("Appearance"), DefaultValue("Anonymous"), Localizable(true), Browsable(false), Description("Sets or gets the name of the user that's chatting in this instance of the control")]
	public string UserName
	{
		get
		{
			string s = (ViewState["UserName"]).ToString();
			if (s == null)
			{
				return "Anonymous";
			}
			else
			{
				return s;
			}
		}

		set
		{
			ViewState["UserName"] = value;
		}
	}

	/// <summary>
	/// Sets or gets the the conversation with which this instance of the control is associated
	/// </summary>
	/// <returns></returns>
	/// <remarks>Some web sites may decide to have multiple chat rooms, each with different topics</remarks>
	[Bindable(true), Category("Behavior"), DefaultValue("general"), Localizable(true), Description("Sets or gets the the conversation topic with which this instance of the control is associated")]
	public string ChatTopic
	{
		get
		{
			string s = (ViewState["ChatTopic"]).ToString();
			if (s == null)
			{
				return "general";
			}
			else
			{
				return s;
			}
		}

		set
		{
			ViewState["ChatTopic"] = value;
		}
	}

#endregion

    	protected override void RenderContents(HtmlTextWriter output)
	{

		//create the containing table
		Table tbl = new Table();
		tbl.Height = this.Height;
		tbl.Width = this.Width;
		tbl.ToolTip = this.ToolTip;
		tbl.Style.Add("overflow", "scroll");
		tbl.Style.Add("position", "absolute");
		TableCell td = new TableCell();
		td.ColumnSpan = 2;
		td.Height = new Unit(95, UnitType.Percentage);
		TableRow tr = new TableRow();
		tr.Cells.Add(td);
		tbl.Rows.Add(tr);

		//create the message display panel
		_pnl.Width = new Unit(99, UnitType.Percentage);
		_pnl.Height = new Unit(100, UnitType.Percentage);
		_pnl.BorderStyle = WebControls.BorderStyle.Solid;
		_pnl.BorderWidth = new Unit(1, UnitType.Pixel);
		_pnl.Style.Add(HtmlTextWriterStyle.Overflow, "scroll");
		td.Controls.Add(_pnl);

		//create a new table row for textbox & button
		tr = new TableRow();
		tbl.Rows.Add(tr);
		td = new TableCell();
		td.Width = new Unit(95, UnitType.Percentage);
		tr.Cells.Add(td);

		//create the button 
		Button btn = new Button();
		btn.ID = this.ID + "_button";
		btn.UseSubmitBehavior = false;
		btn.Text = "Send";

		//create the textbox
		_txt.Width = new Unit(99, UnitType.Percentage);
		_txt.BorderColor = Drawing.Color.Black;
		_txt.BorderWidth = new Unit(1, UnitType.Pixel);
		_txt.Attributes["autocomplete"] = "off";
		_txt.Attributes["onkeydown"] = "if ((event.keyCode == 13)) {document.getElementById('" + btn.ClientID + "').click();return false;} else return true;";
		_txt.Attributes["onkeypress"] = "if (event.keyCode==124 || event.keyCode==126) event.keyCode=0;"; //prevent reserved (delimiter) characters from being entered
		td.Controls.Add(_txt);

		//create the final table cell
		td = new TableCell();
		td.Controls.Add(btn);
		tr.Cells.Add(td);

		//Attach button's client event to the JavaScript functions
		btn.Attributes["OnClick"] = "DoChatCallBack(document.getElementById('" + _txt.ClientID + "'));";

		tbl.RenderControl(output);
	}
    
    private void WebChat_Load(object sender, System.EventArgs e)
	{
		_pnl.ID = this.ID + "_chatpanel";
		_txt.ID = this.ID + "_chattextbox";

		if (this.Visible)
		{
			//Retrieve the JavaScript functions 
			//that will recieve the result
			string sCallBack = My.Resources.Callback.Replace("WebChat1", this.ID);
			if (this.CallBackInterval != 2)
			{
				sCallBack = sCallBack.Replace("2000", (this.CallBackInterval * 1000).ToString());
			}
			Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "CalledBack", sCallBack, true);

			//Now generate the function that initiates 
			//the client side callback
			StringBuilder sb = new StringBuilder();
			sb.Append(System.Environment.NewLine);
			sb.Append("function DoChatCallBack(txt)");
			sb.Append(System.Environment.NewLine);
			sb.Append("{");
			sb.Append("var msg = ''; if (txt != null) msg=txt.value; ");
			sb.Append(System.Environment.NewLine);
			sb.Append(Page.ClientScript.GetCallbackEventReference(this, "WebChatMaxMsgID + '||' + msg", "CalledBack", "ErrCalledBack"));
			sb.Append(";");
			sb.Append(System.Environment.NewLine);
			sb.Append("if (txt != null) txt.value='';");
			sb.Append(System.Environment.NewLine);
			sb.Append("}");
			string sDoCallBack = sb.ToString();
			Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "DoChatCallBack", sDoCallBack, true);

			//set initial focus (if configured to do so)
			if (this.InitialFocus && this.Enabled)
			{
				Page.ClientScript.RegisterStartupScript(this.GetType(), "ChatFocus", "document.getElementById('" + _txt.ClientID + "').focus();", true);
			}

			//emit JavaScript function call that initializes 
			//the control and joins the user into the conversation
			Page.ClientScript.RegisterStartupScript(this.GetType(), "ChatEnter", "ChatEnter();", true);
		}
	}



#region  Callback (AJAX) Code 
	public delegate void ChatterEventHandler(ref string ChatText);
	public event ChatterEventHandler Chatter;

	void System.Web.UI.ICallbackEventHandler.RaiseCallbackEvent(string eventArgument)
	{
		RaiseCallbackEvent(eventArgument);
	}
	public void RaiseCallbackEvent(string eventArgument)
	{
		Debug.WriteLine("Callback " + System.DateTime.Now.ToLongTimeString());

		//extract the last message id that was received by the client
		//so that we can pass back only the new messages
		string[] aryArgs = eventArgument.Split("||".ToCharArray());
		_LastMsgID = VBCasts.CLng(aryArgs[0]);
		eventArgument = aryArgs[2].Trim();

		string WebChatTopic = "WebChat_Conversation_" + this.ChatTopic;

		//Raise Chatter event if new message was sent
		if (eventArgument.Length > 0)
		{
			if (Chatter != null)
				Chatter(eventArgument);
		}

		//Massage the message cosmetically
		string ModifiedMsgText = "<b><i>" + this.UserName + "</i></b>: " + eventArgument;

		//Does the conversation need instantiation?
		if (Context.Application[WebChatTopic] == null)
		{
			//no existing queue found so create a new queue
			Debug.WriteLine("Creating new queue");
			_Messages = new System.Collections.Generic.Queue<ChatMessage>();
			if (eventArgument.Trim().Length > 0)
			{
				ChatMessage Msg = new ChatMessage(1, System.DateTime.Now, ModifiedMsgText, this.UserName);
				_Messages.Enqueue(Msg);
			}
			Context.Application.Lock();
			Context.Application[WebChatTopic] = _Messages;
			Context.Application[WebChatTopic + "_MAX"] = 1;
			Context.Application.Unlock();
		}
		else //existing Queue found, so use it
		{
			Context.Application.Lock();
			_Messages = ((System.Collections.Generic.Queue<ChatMessage>)(Context.Application[WebChatTopic]));
			if (eventArgument.Trim().Length > 0)
			{
				long Max = VBCasts.CLng(Context.Application[WebChatTopic + "_MAX"]);
				Max += 1;
				ChatMessage Msg = new ChatMessage(Max, System.DateTime.Now, ModifiedMsgText, this.UserName);

				//keep track of the max message id
				Context.Application[WebChatTopic + "_MAX"] = Max;

				//place new message on the stack
				_Messages.Enqueue(Msg);

				//purge stale messages
				if (_Messages.Count > _HistoryCapacity)
				{
					Msg = _Messages.Dequeue();
				}

			}

			//put the queue back into application state
			Context.Application[WebChatTopic] = _Messages;
			Context.Application.Unlock();
		}
	}

	string System.Web.UI.ICallbackEventHandler.GetCallbackResult()
	{
		return GetCallbackResult();
	}

    public string GetCallbackResult()
	{
		string retval = "";

		StringBuilder sb = new StringBuilder();
		foreach (ChatMessage Msg in _Messages)
		{
			if (Msg.MessageID > _LastMsgID)
			{
				//Build the return string 
				//containing recent chat messages
				sb.Append(Msg.MessageID.ToString());
				sb.Append("||"); //column delimiter
				sb.Append(Msg.MessageText);
				//sb.Append("||") 'column delimiter
				//sb.Append(Msg.MessageDateTime.ToString)
				//sb.Append("||") 'column delimiter
				//sb.Append(Msg.MessageSender)
				sb.Append("~~"); //row delimeter
			}
		}
		retval = sb.ToString();
		return retval;
	}

#endregion
}
